package erfgame.core.world.entity.activity;

import java.awt.Point;
import java.util.List;

import javax.swing.SwingUtilities;


import erfgame.core.CompassDirection;
import erfgame.core.Point3D;
import erfgame.core.dao.ObjectDAO;
import erfgame.core.entity.Activity;
import erfgame.core.entity.Entity;
import erfgame.core.entity.Orientable;
import erfgame.core.entity.Physical;
import erfgame.core.entity.State;
import erfgame.core.entity.Stateful;
import erfgame.core.event.Action;
import erfgame.core.event.Event;
import erfgame.core.graphics.renderer.RendererContext;
import erfgame.core.path.PathCalculator;
import erfgame.core.path.PathElement;
import erfgame.core.world.World;
import erfgame.core.world.WorldContext;
import erfgame.core.world.WorldDAOManager;
import erfgame.core.world.terrain.BlockEntityPositions;
import erfgame.core.world.terrain.BlockGridTiles;
import erfgame.core.world.terrain.TerrainUtils;
import erfgame.core.world.terrain.WalkablePath;

public class WalkActivity implements Activity {
	
	private Entity entity;
	private Point toGridCoordinates;
	private int toGridIndex;
	
	private PathCalculator pathCalculator;
	private List<PathElement> path;
	private int pathIndex;
	private int pointIndex;
	private int speed;
	
	private WorldContext worldContext;
	private long duration;
	
	private Event currentEvent;
	
	private WorldDAOManager worldDAOManager;
	private RendererContext rendererContext;
	
	private State walkState;

	public WalkActivity( 
			Entity entity, 
			Point toGridCoordinates, 
			int toGridIndex,
			PathCalculator pathCalculator,
			long duration,
			State walkState, 
			WorldContext worldContext, 
			WorldDAOManager worldDAOManager, 
			RendererContext rendererContext
	) {
		this.entity = entity;
		// TODO : extract speed from the entity type
		this.speed = 2;
		this.toGridCoordinates = toGridCoordinates;
		this.toGridIndex = toGridIndex;
		this.pathCalculator = pathCalculator;
		this.worldContext = worldContext;
		this.duration = duration;
		this.worldDAOManager = worldDAOManager;
		this.rendererContext = rendererContext;
		this.walkState = walkState;
	}
	
	public boolean start() {
		World world = worldContext.getCurrentWorld();
		// try and work out what grid the entity is on
		Point3D position = ((Physical)entity).getPosition();
		Point from = world.getGridCoordinates(
				position.x, position.y
		);
		
		// TODO from index
		int fromGridIndex = 0;
		
		this.path = pathCalculator.calculatePath(
				entity,
				from.x, 
				from.y, 
				fromGridIndex, 
				toGridCoordinates.x, 
				toGridCoordinates.y, 
				toGridIndex, 
				0, 0, 
				world.getBlocksX(), world.getBlocksY()
		);
		// TODO : watch for terrain changes along the path		
		if( path != null && path.size() > 0 ) {
			this.pathIndex = path.size()-1;
			this.pointIndex = 0;
			addNextEvent(world);
			return true;
		} else {
			return false;
		}
	}
	
	private void addNextEvent( World world ) {
		Action action = getNextStep( duration, world );
		if( action != null ) {
			currentEvent = new Event(world.getWorldTime() + duration, action );
			world.getEventQueue().addEvent(currentEvent);
		}
	}
	
	private Action getNextStep( long duration, final World world ) {
		// TODO : don't cheat!!
		// TODO : bugs with path start/finish grids
		
		List<Point3D> points = null;
		PathElement pathElement = null;
		int steps = this.speed;
		boolean completed = false;
		
		while( steps > 0 ) {
			boolean rebuild;
			if( points != null ) {
				steps--;
				pointIndex++;
				if( pointIndex >= points.size() ) {
					pathIndex--;
					if( pathIndex >= 0 ) {
						pointIndex = 0;
						rebuild = true;
					} else {
						pointIndex = points.size() - 1;
						completed = true;
						break;
					}
				} else {
					rebuild = false;
				}
			} else {
				rebuild = true;
			}
			if( rebuild ) {
				pathElement = path.get(pathIndex);
				int px = pathElement.getX();
				int py = pathElement.getY();
				
				int bx = px >> world.getXPathTilesPerBlockShift();
				int by = py >> world.getYPathTilesPerBlockShift();
				
				int localpx = px & world.getXPathTilesPerBlockMask();
				int localpy = py & world.getYPathTilesPerBlockMask();
				
				// get the grid and find the index
				ObjectDAO<Point, BlockGridTiles> gridTilesDAO = worldDAOManager.getGridTilesDAO(world);
				
				Point bp = new Point( bx, by );
				
				BlockGridTiles tiles = gridTilesDAO.retrieveObject(bp);
				WalkablePath walkablePath;
				walkablePath = tiles.getPath(localpx, localpy, pathElement.getIndex());
				points = walkablePath.getPoints();				
			}
		} 
		
		if( points != null ) {
			final Point3D position;
			final boolean finalCompleted = completed;
			if( !pathElement.isReversed() ) {
				position = points.get( points.size() - pointIndex - 1 );
			} else {
				position = points.get( pointIndex );
			}
			return new Action() {
				
				public String toString() {
					return "walk-action{entity="+entity+",to="+position+",last="+finalCompleted+"}";
				}
				
				public void perform() {
					final ObjectDAO<Long, Entity> entityDAO = worldDAOManager.getEntityDAO(world);
					final ObjectDAO<Point3D, BlockEntityPositions> blockEntityPositionsDAO;
					blockEntityPositionsDAO = worldDAOManager.getBlockEntityPositionsDAO(world);
					//TODO : up to three separate events are going to be fired here (state, orientation, position, it would be nice to aggregate those into a single event)
					// set state to walking (crawling, swimming, flying, whatever)
					if( entity instanceof Stateful ) {
						State state;
						if( finalCompleted ) {
							state = null;
						} else {
							state = walkState;
						}
						((Stateful)entity).setCurrentState(state, world.getWorldTime());
					}
					// also want to set orientation
					if( entity instanceof Orientable ) {
						// assume that they can only face in oblique directions
						Orientable o = (Orientable)entity;
						Point3D oldPosition = ((Physical)entity).getPosition();
						Point3D newPosition = position;
						CompassDirection facing = null;
						if( newPosition.x > oldPosition.x ) {
							if( newPosition.y > oldPosition.y ) {
								facing = CompassDirection.southEast;
							} else if( newPosition.y < oldPosition.y ) {
								facing = CompassDirection.northEast;
							} else {
								facing = CompassDirection.east;
							}
						} else if( newPosition.x < oldPosition.x ) {
							if( newPosition.y > oldPosition.y ) {
								facing = CompassDirection.southWest;
							} else if( newPosition.y < oldPosition.y ) {
								facing = CompassDirection.northWest;
							} else {
								facing = CompassDirection.west;
							}				
						} else {
							if( newPosition.y > oldPosition.y ) {
								facing = CompassDirection.south;
							} else if( newPosition.y < oldPosition.y ) {
								facing = CompassDirection.north;
							} else {
								facing = o.getOrientation();
							}				
						}
						o.setOrientation( facing );
					}
					// TODO : this probably isn't entirely correct
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							TerrainUtils.moveEntity(
									world, 
									entity, 
									position, 
									blockEntityPositionsDAO, 
									entityDAO, 
									rendererContext
							);
							if( !finalCompleted ) {
								addNextEvent(world);
							} 						
						}
					});
				}
			};
		} else {
			return null; 
		}


	}

	public boolean stop() {
		if( currentEvent != null ) {
			worldContext.getCurrentWorld().getEventQueue().removeEvent(currentEvent);
			currentEvent = null;
			return true;
		} else {
			//?? should never happen
			throw new IllegalStateException( "current event is null" );
		}
	}

}
